package com.sky.aspect;
// 自动填充切面

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.apache.bcel.classfile.Signature;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;


//面向切面编程（AOP）通俗的说就是一个项目各个功能之间有几个通过的功能比如日志记录 安全控制等等。这个不在对应功能的主体逻辑中 所以我们可以把这段逻辑提取并封装，减少系统的重复代码，降低模块间的耦合度，并有利于未来的可拓展性和可维护性。
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")  //你写这个切点是为了 精确控制：只拦截那些需要公共字段自动填充的 Mapper 方法。避免无关方法被反射赋值，提高效率 保证 AOP 不会干扰普通查询、删除等操作
    public void autoFillPointCut() {
    }


    //前置通知 在目标方法的逻辑还未执行时 先执行下方逻辑代码  在通知中进行公共字段的自动填充
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        log.info("开始进行公共字段的自动填充");

        //获取当前被拦截的方法上的数据库操作类型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();  // 得到一个 MethodSignature 对象。它知道被拦截的方法是 dishMapper.insert(dish)。     获取方法签名 最终得到的 signature：封装了被拦截的目标方法的完整信息，例如：方法名 insert 方法参数 Dish 方法返回类型 void  方法所在类 DishMapper
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); // AOP 框架在底层执行类似 DishMapper.class.getDeclaredMethod("insert", Dish.class) 的操作。获取方法上的注解对象。  signature.getMethod()：返回当前被拦截的方法的 反射 Method 对象。对应你的 Mapper 接口方法：void insert(Dish dish)。                                                                                  //getAnnotation(AutoFill.class)：获取方法上标注的 @AutoFill 注解实例。autoFill 就是这个注解实例，可以调用 autoFill.value() 获取其中的 OperationType。
        OperationType operationType = autoFill.value();// 获取数据库操作类型  返回注解中指定的操作类型。 
        //获取当前被拦截的方法的参数--实体对象
        Object[] args=joinPoint.getArgs();  //args[0] → 传入 Mapper 方法的实体对象，例如 Dish。后续通过 反射调用 setter 方法 给实体对象赋值。
        if (args==null||args.length==0){
            return;
        }
//| 变量                      | 类型                | 含义            | 示例                                |
//| ----------------------- | ----------------- | ------------- | --------------------------------- |
//| `signature`             | `MethodSignature` | 封装了被拦截方法的签名信息 | `insert(Dish dish)`               |
//| `signature.getMethod()` | `Method`          | 反射方法对象        | `DishMapper.insert(Dish)`         |
//| `autoFill`              | `AutoFill`        | 方法上的注解实例      | `@AutoFill(OperationType.INSERT)` |
//| `operationType`         | `OperationType`   | 注解中的数据库操作类型   | `INSERT`                          |
//| `args`                  | `Object[]`        | 方法的实际参数数组     | `[dish]`                          |
//| `args[0]`               | `Object`          | 实体对象          | `Dish` 实例                         |

        Object entity=args[0];
        //准备赋值的数据
        LocalDateTime now=LocalDateTime.now();
        Long currentId= BaseContext.getCurrentId();

        //根据当前不同的操作类型，为对应的属性通过反射进行赋值
        if(operationType==OperationType.INSERT){
            try{
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME,LocalDateTime.class);  //entity.getClass()：获取实体对象的类类型如果 entity 是 Dish，就返回 Dish.class。getDeclaredMethod(String name, Class<?>... parameterTypes)：通过方法名和参数类型获取反射                                                                                                                                        //Method 对象。这样就拿到了 可以操作对象属性的 Method，还没执行，只是准备好了。
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                //通过反射给对应的属性赋值
                setCreateTime.invoke(entity,now);  //作用：在对象 obj 上执行该方法，传入参数 args
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            }catch (Exception e){
                e.printStackTrace();
            }

        }else if(operationType==OperationType.UPDATE){
            try{
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                //通过反射给对应的属性赋值
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}
